//
// smtp.cpp
// libprotea
// Tools Library for the Protea Project / Simple Mail Transfer Protocol (SMTP) used in server-side reporting
// Copyright (C) 2003, 2004, 2005 eXegeSys, Inc.
// Copyright (C) 2008 Bruce A. James
//
// This program is part of the Protea Project.
//
// The Protea Project is free software; you can redistribute it and/or modify it 
// under the terms of the GNU General Public License as published by the 
// Free Software Foundation, either version 3 of the License, or (at your 
// option) any later version.
//
// The Protea Project is distributed in the hope that it will be useful, but 
// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 
// for more details.
// 
// You should have received a copy of the GNU General Public License along 
// with this program.  If not, see <http://www.gnu.org/licenses/>.
// 
// Please send correspondence to:
// 
// theproteaproject@gmail.com
//

#include "../defines.h"
#include "../OSDep/socket.h"
#include "../OSDep/files.h"
#include "../Security/base64.h"
#include "smtp.h"

const long LF = 10;
const long CR = 13;

XCMessage::XCMessage() {
	subject = NULL;
	from = NULL;
	msg_data = NULL;
	attachments = NULL;
	rept = NULL;
};

XCMessage::~XCMessage() {
	if (subject)
		free( subject );
	if (from)
		free( from );
	if (msg_data)
		free( msg_data );
};

int XCMessage::GetResponse( XCSocketTransport* socket ) {
	char data[ 255 ];
	char* temp = data;
	int counter = 0;
	int stage = 0;
	char* first_space = NULL;
	char* end = NULL;
	bool negate = false;

	int result;
	do {
		result = socket->Recv( temp, 1 );
		if (result) {
			if (stage == 1 && *temp == LF)
				stage = 2;
			else if (stage == 0 && *temp == CR) {
				end = temp;
				stage = 1;
			} else
				stage = 0;
			if (first_space == NULL) {
				if (*temp == ' ')
					first_space = temp;
				else if (*temp == '-') {
					first_space = temp;
					negate = true;
				};
			};

			temp++;
		};
	} while (result > 0 && stage != 2);

	if (end)
		*end = NULL;

//	printf( "*** RESULT = '%s' ***\n", data );
	if (first_space != NULL) {
		*first_space = NULL;
		last_response = negate ? -atoi( data ) : atoi( data );
	} else {
		// We must have had a socket error
		last_response = -1;
	};

	return last_response;
};



void XCMessage::Send( const char* host ) {
	XCSocketTransport socket( NULL );
	char hostname[ 255 ];
	XCGetHostName( hostname, 255 );

	socket.Create( AF_INET, SOCK_STREAM, 0 );

	// If no host is supplied, default to localhost
	socket.Connect( host ? host : "127.0.0.1", 25 );
	try {
		// Look for initial 220 result
		if (GetResponse( &socket ) != 220)
			throw XCSMTPException( last_response );

		// Send EHLO command
		socket.Send( "EHLO " );
		socket.Send( hostname );
		socket.Send( "\r\n" );

		int gresult;
		do {
			gresult = GetResponse( &socket );
		} while (gresult == -250);

		// FALLBACK
		if (gresult == 500) {
			// Send HELO command
			socket.Send( "HELO " );
			socket.Send( hostname );
			socket.Send( "\r\n" );
			gresult = GetResponse( &socket );
		};

//		printf( "FINAL RESULT = %d\n", gresult );
		if (gresult != 250)
			throw XCSMTPException( last_response );

		socket.Send( "MAIL FROM:<" );
		socket.Send( from );
		socket.Send( ">" );
		socket.Send( "\r\n" );

		// Look for 250 result
		if (GetResponse( &socket ) != 250)
			throw XCSMTPException( last_response );

		XCMessageRept* temp = rept;
		while (temp) {
			socket.Send( "RCPT TO:" );
			if (temp->nicename) {
				socket.Send( temp->nicename );
			};
			socket.Send( "<" );
			socket.Send( temp->to );
			socket.Send( ">" );
			socket.Send( "\r\n" );

			// Look for 250 result
			if (GetResponse( &socket ) != 250)
				throw XCSMTPException( last_response );

			temp = temp->next;
		};

		socket.Send( "DATA\r\n" );
		if (GetResponse( &socket ) != 354)
			throw XCSMTPException( last_response );

		if (nicename) {
			socket.Send( "From:<" );
			socket.Send( from );
			socket.Send( ">\r\n" );
		};

		if (subject) {
			socket.Send( "Subject:" );
			socket.Send( subject );
			socket.Send( "\r\n" );
		};

		socket.Send( "To:" );
		temp = rept;
		bool first = true;
		while (temp) {
			if (!first)
				socket.Send( "," );
			socket.Send( temp->to );
			temp = temp->next;
			first = false;
		};
		socket.Send( "\r\n" );

		socket.Send( "MIME-Version: 1.0\r\n" );
		socket.Send( "Content-Type: multipart/mixed; boundary=gc0y0pkb9ex\r\n" );
		socket.Send( "--gc0y0pkb9ex\r\n" );

//		printf( "Sending: %s\n", msg_data );
		socket.Send( msg_data );

		// Now, do attachments
		XCAttachment* atemp = attachments;
		while (atemp) {
//			printf( "ADD ATTACHMENT: %s\n", atemp->file );
			socket.Send( "\r\n--gc0y0pkb9ex\r\n" );
			socket.Send( "Content-Type: application/octet-stream; name=\"" );
			socket.Send( atemp->file );
			socket.Send( "\"\r\n" );
			socket.Send( "Content-Transfer-Encoding: base64\r\n" );
			socket.Send( "Content-Disposition: attachment; filename=\"" );
			socket.Send( atemp->file );
			socket.Send( "\"\r\n\r\n" );

			// Now send base64 encoded data
			const long buffer_size = 78;
			char in_buffer[ buffer_size ];
			char out_buffer[ 1600 ];
			XCFileTransport input( NULL );
			XCBase64 base64;
			input.Open( atemp->file );
			int size;
			int total = input.GetSize();
			while (!input.Eof()) {
				size = input.Recv( in_buffer, total > buffer_size ? buffer_size : total );
				total -= size;

				base64.Encode( out_buffer, (const unsigned char*)in_buffer, size );
				socket.Send( out_buffer );
//				printf( "Output: %s\n", out_buffer );
				socket.Send( "\r\n" );
			};
			input.Close();

			atemp = atemp->next;
		};

		// Terminate the mime section
		socket.Send( "\r\n--gc0y0pkb9ex--\r\n" );
		socket.Send( "\r\n.\r\n" );
	} catch( ... ) {
		socket.Send( "QUIT" );
		socket.Close();
		throw;
	};
	socket.Send( "QUIT" );
	socket.Close();
};



void XCMessage::SetSubject( const char* new_subject ) {
	if (subject)
		free( subject );
	subject = strdup( new_subject );
};


void XCMessage::SetFrom( const char* new_from ) {
	if (from)
		free( from );
	from = strdup( new_from );
};


void XCMessage::SetData( const char* new_data ) {
	if (msg_data)
		free( msg_data );
	msg_data = strdup( new_data );
};


void XCMessage::AddRecp( const char* user, const char* name ) {
	XCMessageRept* new_rept = new XCMessageRept;
	new_rept->to = strdup( user );
	if (name)
		new_rept->nicename = strdup( name );
	else
		new_rept->nicename = NULL;
	new_rept->next = rept;
	rept = new_rept;
};



void XCMessage::AddAttachment( const char* attach_filename ) {
	XCAttachment* new_attach = new XCAttachment;
	new_attach->file = strdup(attach_filename);
	new_attach->next = attachments;
	attachments = new_attach;
};





////
//
// XCSMTPException
//
////
XCSMTPException::XCSMTPException( int number ) {
	exception_number = number;
};


void XCSMTPException::GetDescription( char* data ) {
	switch( exception_number ) {
		// Socket
		case -1:		strcpy( data, "Socket error connecting to mail server" ); break;

		// Common
		case 421:	strcpy( data, "Service not available, closing transmission channel" ); break;
		case 450:	strcpy( data, "Requested mail action not taken: mailbox unavailable" ); break;
		case 451:	strcpy( data, "Requested action aborted: local error in processing" ); break;
		case 452:	strcpy( data, "Requested action not taken: insufficient system storage" ); break;
		case 500:	strcpy( data, "Syntax error, command unrecognized" ); break;
		case 501:	strcpy( data, "Syntax error in parameters or arguments" ); break;
		case 502:	strcpy( data, "Command not implemented" ); break;
		case 503:	strcpy( data, "Bad sequence of commands" ); break;
		case 504:	strcpy( data, "Command parameter not implemented" ); break;
		case 550:	strcpy( data, "Requested action not taken: mailbox unavailable" ); break;
		case 551:	sprintf( data, "User not local; please try ???" ); break;
		case 552:	strcpy( data, "Requested mail action aborted: exceeded storage allocation" ); break;
		case 553:	strcpy( data, "Requested action not taken: mailbox name not allowed" ); break;
		case 554:	strcpy( data, "Transaction failed" ); break;

		// Uncommon
		case 211:	strcpy( data, "System status, or system help reply" ); break;
		case 214:	strcpy( data, "Help message" ); break;
		case 220:	strcpy( data, "Service ready" ); break;
		case 221:	strcpy( data, "Service closing transmission channel" ); break;
		case 250:	strcpy( data, "Requested mail action okay, completed" ); break;
		case 251:	sprintf( data, "User not local; will forward to ???" ); break;
		case 354:	strcpy( data, "Start mail input; end with ." ); break;

		// Everything else
		default:
			sprintf( data, "Unknown smtp exception: %d", exception_number );
			break;
	};
};
